给你的SwipeRefreshLayout换个免费的皮肤~
作者:giswangsj, 链接:https://juejin.cn/post/6995834032458039332
SwipeRefreshLayout
是Androidx提供了提供的下拉刷新组件,具体如何使用就不说了,相信大家也都经常用。
1、效果
首先看一下SwipeRefreshLayout的默认效果:
为了不耽误你的时间,先看一下最终效果:
2 、常用方法
尽管SwipeRefreshLayout
提供了setColorSchemeResources()
方法可以设置几个'圈圈'
的颜色,但效果还是差强人意,作为高级代码搬运工的我实在看不下去了。
于是帮SwipeRefreshLayout
换个免费的皮肤吧。
3、分析
SwipeRefreshLayout
的代码很简单,就三个类
SwipeRefreshLayout
就是我们在xml中使用的布局,根据情况拦截并消费手势事件,更新Loading视图的位置,控制其转动角度等,具体逻辑不再这里展开,有需要的大佬自己去看吧。CircleImageView
下拉时拉出来的圆圈CircularProgressDrawable
下拉及刷新时转动的圈圈
他们三个关系非常紧密,在SwipeRefreshLayout
中创建了CircleImageView
和CircularProgressDrawable
,将CircularProgressDrawable
设置给CircleImageView
,然后又将CircleImageView
添加到了SwipeRefreshLayout
中,代码如下:
public class SwipeRefreshLayout {
// 1,声明一个CircleImageView
CircleImageView mCircleVew;
// 1,声明一个CircularProgressDrawable
CircularProgressDrawable mProgress;
public SwipeRefreshLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
...
// 构造方法中初始化
createProgressView();
...
}
private void createProgressView() {
// 2,初始化mCircleView
mCircleView = new CircleImageView(getContext());
// 2,初始化mProgress
mProgress = new CircularProgressDrawable(getContext());
// 3,将mProgress设置为mCircleView的Drawable
mCircleView.setImageDrawable(mProgress);
mCircleView.setVisibility(View.GONE);
// 4,添加mCircleView在当前ViewGroup中
addView(mCircleView);
}
}
结论:
1,由于 CircularProgressDrawable
是控制这个'皮肤'的关键,因此需要先定义一个Drawable,并实现相应的效果。2,由于 SwipeRefreshLayout
没有提供扩展的接口,无法直接使用自定义的Drawable,因此需要将SwipeRefreshLayout
拷贝一份,修改其中的代码。3, CircleImageView
貌似是可以使用的,但是其中添加了一个半透明背景,无法通过设置的方式去除,因此也需要重写一下。
4、撸码
1,自定义 LoadingDrawable
自定义一个Drawable
并实现Animatable
接口,在其中绘制图形及动画,具体如何实现可以参考LoadingDrawable
,效果如下:
2,重写 SwipeRefreshLayout
将SwipeRefreshLayout
的代码拷贝一份,重命名为MySwipeRefreshLayout
,如下:
public class MySwipeRefreshLayout extends ViewGroup implements NestedScrollingParent3,
NestedScrollingParent2, NestedScrollingChild3, NestedScrollingChild2, NestedScrollingParent,
NestedScrollingChild {
...
CircularProgressDrawable mProgress;
...
}
只需要将其中的mProgress
改为我们上面定义的LoadingDrawable
即可,然后删除一些无用的代码即可。看一下效果:
这样就有了我们想要的效果,但是发现动画中有一个圆形的阴影背景。分析一下,首先这个背景我不是我们定义的Drawable中的,那就看看Drawable所在的CircleImageView
吧,代码如下:
class CircleImageView extends ImageView {
...
CircleImageView(Context context) {
super(context);
// 创建一个OvalShape的ShapeDrawable
ShapeDrawable circle = new ShapeDrawable(new OvalShape());
// 绘制
ViewCompat.setBackground(this, circle);
}
private static class OvalShadow extends OvalShape {
...
@Override
public void draw(Canvas canvas, Paint paint) {
// 绘制圆形
canvas.drawCircle(x, y, x, mShadowPaint);
canvas.drawCircle(x, y, x - mShadowRadius, paint);
}
}
}
可见这个背景是在CircleImageView
中绘制的,由于CircleImageView
没有提供对应的方法控制是否绘制背景,因此要想去掉这个背景就需要自定义一个CircleImageView
。
3,自定义CircleImageView
由于我们的目的是要去掉背景,因此这个就非常简单了,只需要把绘制背景的代码删掉即可:
public class MyCircleImageView extends androidx.appcompat.widget.AppCompatImageView {
private Animation.AnimationListener mListener;
public MyCircleImageView(Context context) {
super(context);
}
...
public void setAnimationListener(Animation.AnimationListener listener) {
mListener = listener;
}
@Override
public void onAnimationStart() {
super.onAnimationStart();
if (mListener != null) {
mListener.onAnimationStart(getAnimation());
}
}
@Override
public void onAnimationEnd() {
super.onAnimationEnd();
if (mListener != null) {
mListener.onAnimationEnd(getAnimation());
}
}
}
然后将第二步创建的MySwipeRefreshLayout
中的CircleImageView
换成MyCircleImageView
即可,直接看效果吧:
4,进一步优化
给SwipeRefreshLayout
的换肤已经完成,但是感觉哪里总是不对劲,再往下拖动时让布局跟着一起动效果会不会更好呢?试试吧
思路也很简单,就是再下拉时根据拉动的距离让内部的子View ScrollTo
到指定位置,最后松手时ScrollTo
到原来的位置即可。
具体代码分两步:
1,跟着滑动
在onTouchEvent()
的ACTION_MOVE
事件中,如果时Dragged状态就会调用moveSpinner(overscrollTop)
,在其中添加代码即可:
private void moveSpinner(float overscrollTop) {
float originalDragPercent = overscrollTop / mTotalDragDistance;
float dragPercent = Math.min(1f, Math.abs(originalDragPercent));
float adjustedPercent = (float) Math.max(dragPercent - .4, 0) * 5 / 3;
float extraOS = Math.abs(overscrollTop) - mTotalDragDistance;
float slingshotDist = mCustomSlingshotDistance > 0
? mCustomSlingshotDistance
: (mUsingCustomStart
? mSpinnerOffsetEnd - mOriginalOffsetTop
: mSpinnerOffsetEnd);
float tensionSlingshotPercent = Math.max(0, Math.min(extraOS, slingshotDist * 2)
/ slingshotDist);
float tensionPercent = (float) ((tensionSlingshotPercent / 4) - Math.pow(
(tensionSlingshotPercent / 4), 2)) * 2f;
float extraMove = slingshotDist * tensionPercent * 2;
int targetY = mOriginalOffsetTop + (int) ((slingshotDist * dragPercent) + extraMove);
...
// 增加以下代码即可
if (targetY >= 0) {
((ViewGroup) mTarget).getChildAt(0).scrollTo(0, -targetY / 2);
}
}
只需要在targetY>=0
时执行scrollTo()
即可。
2,返回原处
在onTouchEvent()
的ACTION_UP事件中,在Dragged状态松手时会执行finishSpinner()
,在其中将子View回复到原位即可:
private void finishSpinner(float overscrollTop) {
// 返回原处
((ViewGroup) mTarget).getChildAt(0).scrollTo(0, 0);
...
}
如果你觉得scrollTo(0, 0)
太突兀,可以搞个属性动画让回弹速度变慢一点,这里就不多说了,看下效果吧:
5、最后
如果你想进一步扩展,比如加上一些文字,最近更新时间等也是可以实现的。
Github: https://github.com/wsj1024/FengYunWeather
---END---
更文不易,点个“在看”支持一下👇